﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using EssentialWebFramework.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace EssentialWebFramework.Common.Attributes
{
    /// <summary>
    /// 限制访问频率（针对 Action） 特性
    /// </summary>
    public class ActionThrottleAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 缓存 TAG
        /// </summary>
        public const string CACHE_TAG = "ATA";        

        /// <summary>
        /// 默认 每个时间周期的秒数
        /// </summary>
        private const int DEFAULT_TIME_UNIT_SECONDS = 1;

        /// <summary>
        /// 默认 每个时间周期最多访问次数
        /// </summary>
        private const int DEFAULT_MAX_TIMES_PER_TIME_UNIT = 3;

        /// <summary>
        /// 默认 封禁时间
        /// </summary>
        private const int DEFAULT_FORBIDDEN_MINUTE = 5;

        /// <summary>
        /// 每个时间周期的秒数
        /// </summary>
        private readonly int _timeUnitSeconds;

        /// <summary>
        /// 每个时间周期最多访问次数
        /// </summary>
        private readonly int _maxTimesPerTimeUnit;

        /// <summary>
        /// 封禁时间
        /// </summary>
        private readonly int _forbiddenMinutes;

        /// <summary>
        /// ctor
        /// </summary>
        public ActionThrottleAttribute() : 
            this(DEFAULT_TIME_UNIT_SECONDS,
                DEFAULT_MAX_TIMES_PER_TIME_UNIT, 
                DEFAULT_FORBIDDEN_MINUTE) { }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="timeUnitSeconds">每个时间单位多少 秒</param>
        /// <param name="maxTimesPerTimeUnit">每个时间单位最多多少次</param>
        /// <param name="forbiddenMinutes">封禁时间多少 分钟</param>
        public ActionThrottleAttribute(int timeUnitSeconds, int maxTimesPerTimeUnit, int forbiddenMinutes)
        {
            this._timeUnitSeconds = timeUnitSeconds;
            this._maxTimesPerTimeUnit = maxTimesPerTimeUnit;
            this._forbiddenMinutes = forbiddenMinutes;
        }

        /// <summary>
        /// 覆写 OnActionExecuting
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            ILogger logger = context.HttpContext.RequestServices.GetService<ILogger<ActionThrottleAttribute>>();
            IDistributedCache distributedCache = context.HttpContext.RequestServices.GetService<IDistributedCache>();
            CacheForComponent cache = new CacheForComponent(CACHE_TAG, distributedCache);

            // TODO 可以考虑加入渐进式的限制
            string ipAddress = CommonUtils.FormatIpAddressForCache(context.HttpContext.Connection.RemoteIpAddress.ToString());

            bool isExist = cache.TryGetCacheValue(ipAddress, out int remainingTimes);
            if (isExist)
            {
                if (remainingTimes > 0)
                {
                    cache.SetCacheSlideExpire(ipAddress, --remainingTimes, new TimeSpan(0, 0, _timeUnitSeconds));
                    logger.LogInformation("{0} 可访问次数 - 1, 剩余 {1} 次", ipAddress, remainingTimes);
                }
                else
                {
                    cache.SetCacheAbsoluteExpire(ipAddress, 0, new TimeSpan(0, _forbiddenMinutes, 0));
                    logger.LogInformation("{0} 已经被禁止，{1} 分钟后可继续访问", ipAddress, _forbiddenMinutes);

                    context.Result = new OkObjectResult(new Result 
                    {
                        Success = false, 
                        Msg = "访问过于频繁" 
                    });
                }
            }
            else
            {
                cache.SetCacheSlideExpire(ipAddress, _maxTimesPerTimeUnit - 1, new TimeSpan(0, 0, _timeUnitSeconds));
                logger.LogInformation("为 {0} 设置访问限制 {1}", ipAddress, _maxTimesPerTimeUnit - 1);
            }
        }
    }
}
